﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Drawing;
using Citrix.Cmp;
using System.Reflection;
using System.Windows.Forms;

namespace LegacyApp
{
    /// <summary>
    /// Holds a collection of 'Control' instances that are member variables that belong to a particular form.
    /// The controls are mapped to their original font point size so we can consistently scale the font size
    /// relative to their original size.
    /// </summary>
    public class ScalableFormControls
    {
        private Dictionary<Control, float> _formControls = new Dictionary<Control, float>();

        public Dictionary<Control, float> Controls
        {
            get { return _formControls; }
        }
    }

    /// <summary>
    /// This is a helper to scale the fonts of controls in your WinForms app dynamically according to the
    /// DPI information retrieved from the Citrix Mobile SDK for Windows Apps.
    /// </summary>
    public static class WinformsScalingHelper
    {
        static WinformsScalingHelper()
        {
            DefaultWindowsDpi = 96;
        }

        /// <summary>
        /// This is the default DPI of the developers desktop when they developed their app. The app
        /// was designed for this DPI, so we do all our scaling relative to it. The default value for
        /// this DPI is 96. However if you ever develop apps on a different DPI device you must set
        /// this value to that DPI before using any of the methods on this class.
        /// </summary>
        public static float DefaultWindowsDpi
        {
            get;
            set;
        }

        /// <summary>
        /// This function finds all the controls on the supplied Form that are derived from 'Control'. It
        /// saves them in a ScalableFormControls instance. You can then supply this object in
        /// <see cref="DpiScaleFonts"/> calls to update the font scaling of all the controls according
        /// to the DPI of the current client device.
        /// </summary>
        /// <param name="source">The source form to inspect.</param>
        /// <returns>A container of the controls that belong to the form.</returns>
        public static ScalableFormControls MapControls(Form source)
        {
            ScalableFormControls formControls = new ScalableFormControls();

            var bindingFlags =
                BindingFlags.Instance |
                BindingFlags.NonPublic |
                BindingFlags.Public;

            var fields = typeof(Form1).GetFields(bindingFlags);
            var controlFields = fields.Where(f => typeof(Control).IsAssignableFrom(f.FieldType)).ToList();

            foreach (var field in controlFields)
            {
                Control instance = field.GetValue(source) as Control;
                if (null != instance)
                {
                    float size = 0;
                    if (instance.Font != null)
                    {
                        size = instance.Font.SizeInPoints;
                    }
                    formControls.Controls[instance] = size;
                }
            }

            return formControls;
        }

        /// <summary>
        /// Scales the fonts of the supplied set of controls according to the current DPI of the client device.
        /// So if the current client device has a DPI of 400 and all our controls had a font size of 10pts we
        /// would scale the font size up as follows:
        /// 
        /// (400 / 96) * 10 = 41.67pts
        /// 
        /// Where 96 is the default Windows DPI that the app was originally designed for.
        /// </summary>
        /// <param name="cmp">The Mobile SDK instance we query for DPI state.</param>
        /// <param name="formControls">The set of controls to scale.</param>
        /// <remarks>
        /// Windows has historically always used 96 as its default DPI, so it is the most appropriate default.
        /// However if you designed your app to work with a different default DPI, then you should set
        /// <see cref="DefaultWindowsDpi"/> to that DPI value before you call this function.
        /// </remarks>
        public static void DpiScaleFonts(CmpApi cmp, ScalableFormControls formControls)
        {
            var display = cmp.Display.CapabilitiesDisplayState;
            if (display.Dpi != null)
            {
                float averageDpi = (float)(display.Dpi.X + display.Dpi.Y) / 2;
                float scaling = averageDpi / DefaultWindowsDpi;

                UpdateControlsToScale(scaling, formControls);
            }
            else
            {
                UpdateControlsToScale(1, formControls);
            }
        }

        /// <summary>
        /// Does the work of scaling the fonts of the supplied controls.
        /// </summary>
        /// <param name="scale">The new scale to use.</param>
        /// <param name="formControls">The set of controls to scale, along with their original font sizes.</param>
        private static void UpdateControlsToScale(float scale, ScalableFormControls formControls)
        {
            foreach (var c in formControls.Controls)
            {
                float originalPointSize = c.Value;
                if (originalPointSize != 0)
                {
                    Font src = c.Key.Font;

                    // We scale the font size from the original size chosen by the developer.
                    float targetPointSize = originalPointSize * scale;

                    // Limit the point size range to sensible values.
                    targetPointSize = Math.Max(1, targetPointSize);

                    // Change the font size if required.
                    if (c.Key.Font.SizeInPoints != targetPointSize)
                    {
                        // Clone font with a new size.
                        Font font = new System.Drawing.Font(
                            src.FontFamily.Name,
                            targetPointSize,
                            src.Style,
                            GraphicsUnit.Point,
                            src.GdiCharSet,
                            src.GdiVerticalFont);
                        c.Key.Font = font;
                    }
                }
            }
        }
    }
}
